package com.codeanalysissys.backend.javascript;

import com.codeanalysissys.backend.graphs.cfg.CFNode;
import com.codeanalysissys.backend.graphs.cfg.CFPathTraversal;
import com.codeanalysissys.backend.graphs.cfg.ControlFlowGraph;
import com.codeanalysissys.backend.graphs.pdg.DDEdge;
import com.codeanalysissys.backend.graphs.pdg.DataDependenceGraph;
import com.codeanalysissys.backend.graphs.pdg.PDNode;
import com.codeanalysissys.backend.java.parser.JavaParser;
import com.codeanalysissys.backend.javascript.parser.JavaScriptBaseVisitor;
import com.codeanalysissys.backend.javascript.parser.JavaScriptLexer;
import com.codeanalysissys.backend.javascript.parser.JavaScriptParser;
import ghaffarian.graphs.Edge;
import ghaffarian.nanologger.Logger;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class JavaScriptDDGBuilder {

    // Just for debugging
    private static String currentFile;

    // NOTE: This doesn't handle duplicate class names;
    //       yet assuming no duplicate class names is fair enough.
    //       To handle that, we should use 'Map<String, List<JavaClass>>'
    private static Map<String, JavaScriptClass> allClassInfos;

    //记录所有非class成员函数的函数信息
    private static Map<String, List<MethodDefInfo>> allNonClassFunctionInfos;

    private static Map<String, List<MethodDefInfo>> methodDEFs;

    public static DataDependenceGraph build(InputStream inFile) throws IOException {
        ANTLRInputStream input = new ANTLRInputStream(inFile);
        JavaScriptLexer lexer = new JavaScriptLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JavaScriptParser parser = new JavaScriptParser(tokens);
        ParseTree parseTree = parser.program();
        Logger.info("Done.");

        // Extract the information of all given Java classes
        Logger.info("\nExtracting class-infos ... ");
        allClassInfos = new HashMap<>();
        List<JavaScriptClass[]> filesClasses = new ArrayList<>();
        List<JavaScriptClass> classesList = JavaScriptClassExtractor.extractInfo(parseTree);
        filesClasses.add(classesList.toArray(new JavaScriptClass[classesList.size()]));
        for (JavaScriptClass cls : classesList)
            allClassInfos.put(cls.NAME, cls);
        Logger.info("Done.");

        // Initialize method DEF information
        Logger.info("\nInitializing method-DEF infos ... ");
        methodDEFs = new HashMap<>();
        for (JavaScriptClass[] classArray : filesClasses) {
            for (JavaScriptClass cls : classArray) {
                for (JavaScriptMethod mtd : cls.getAllMethods()) {
                    List<MethodDefInfo> list = methodDEFs.get(mtd.NAME);
                    if (list == null) {
                        list = new ArrayList<>();
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.NAME));
                        methodDEFs.put(mtd.NAME, list);
                    } else {
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.NAME));
                        // no need to do 'methodDEFs.put(...)' again
                    }
                }
            }
        }
        Logger.info("Done.");

        // Analyze method DEF information for imported libraries
//        analyzeImportsDEF(filesClasses);

        // Iteratively, extract USE-DEF info for all program statements ...
        DataDependenceGraph ddg = new DataDependenceGraph();
        //
        Map<ParserRuleContext, Object> pdNode = new IdentityHashMap<>();
        //
        Logger.info("\nIterative DEF-USE analysis ... ");
        boolean changed;
        int iteration = 0;
        do {
            ++iteration;
            changed = false;
            DefUseVisitor defUse = new DefUseVisitor(iteration, filesClasses.get(0), ddg, pdNode);
            defUse.visit(parseTree);
            changed |= defUse.changed;
            Logger.debug("Iteration #" + iteration + ": " + (changed ? "CHANGED" : "NO-CHANGE"));
            Logger.debug("\n========================================\n");
        } while (changed);
        Logger.info("Done.");

        // Build control-flow graphs for all Java files including the extracted DEF-USE info ...
        Logger.info("\nExtracting CFGs ... ");
        ControlFlowGraph cfg = JavaScriptCFGBuilder.build(parseTree, "pdnode", pdNode);
        Logger.info("Done.");

        // Finally, traverse all control-flow paths and draw data-flow dependency edges ...
        Logger.info("\nAdding data-flow edges ... ");
        addDataFlowEdges(cfg, ddg);
        ddg.attachCFG(cfg);
        Logger.info("Done.\n");

        return ddg;
    }

    public static DataDependenceGraph[] buildForAll(File[] files) throws IOException {
        // Parse all JavaScript source files
        Logger.info("Parsing all source files ... ");
        ParseTree[] parseTrees = new ParseTree[files.length];
        for (int i = 0; i < files.length; ++i) {
            InputStream inFile = new FileInputStream(files[i]);
            ANTLRInputStream input = new ANTLRInputStream(inFile);
            JavaScriptLexer lexer = new JavaScriptLexer(input);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            JavaScriptParser parser = new JavaScriptParser(tokens);
            parseTrees[i] = parser.program();
        }
        Logger.info("Done.");

        // Extract the information of all given Java classes
        Logger.info("\nExtracting class-infos ... ");
        allClassInfos = new HashMap<>();
        List<JavaScriptClass[]> filesClasses = new ArrayList<>();
        for (int i = 0; i < files.length; ++i) {
            List<JavaScriptClass> classesList = JavaScriptClassExtractor.extractInfo(files[i].getPath(), parseTrees[i]);
            filesClasses.add(classesList.toArray(new JavaScriptClass[classesList.size()]));
            for (JavaScriptClass cls : classesList)
                allClassInfos.put(cls.NAME, cls);
        }
        Logger.info("Done.");

        // Initialize method DEF information
        Logger.info("\nInitializing method-DEF infos ... ");
        methodDEFs = new HashMap<>();
        for (JavaScriptClass[] classArray : filesClasses) {
            for (JavaScriptClass cls : classArray) {
                for (JavaScriptMethod mtd : cls.getAllMethods()) {
                    List<MethodDefInfo> list = methodDEFs.get(mtd.NAME);
                    if (list == null) {
                        list = new ArrayList<>();
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.NAME));
                        methodDEFs.put(mtd.NAME, list);
                    } else {
                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.NAME));
                        // no need to do 'methodDEFs.put(...)' again
                    }
                }
            }
        }
        Logger.info("Done.");

        // Analyze method DEF information for imported libraries
//        analyzeImportsDEF(filesClasses);

        // Iteratively, extract USE-DEF info for all program statements ...
        DataDependenceGraph[] ddgs = new DataDependenceGraph[files.length];
        for (int i = 0; i < ddgs.length; ++i)
            ddgs[i] = new DataDependenceGraph(files[i].getName());
        //
        Map<ParserRuleContext, Object>[] pdNodes = new Map[parseTrees.length];
        for (int i = 0; i < parseTrees.length; ++i)
            pdNodes[i] = new IdentityHashMap<>();
        //
        Logger.info("\nIterative DEF-USE analysis ... ");
        boolean changed;
        int iteration = 0;
        do {
            ++iteration;
            changed = false;
            for (int i = 0; i < files.length; ++i) {
                currentFile = files[i].getName();
                DefUseVisitor defUse = new DefUseVisitor(iteration, filesClasses.get(i), ddgs[i], pdNodes[i]);
                defUse.visit(parseTrees[i]);
                changed |= defUse.changed;
            }
            Logger.debug("Iteration #" + iteration + ": " + (changed ? "CHANGED" : "NO-CHANGE"));
            Logger.debug("\n========================================\n");
        } while (changed);
        Logger.info("Done.");

        // Build control-flow graphs for all Java files including the extracted DEF-USE info ...
        Logger.info("\nExtracting CFGs ... ");
        ControlFlowGraph[] cfgs = new ControlFlowGraph[files.length];
        for (int i = 0; i < files.length; ++i)
            cfgs[i] = JavaScriptCFGBuilder.build(files[i].getName(), parseTrees[i], "pdnode", pdNodes[i]);
        Logger.info("Done.");

        // Finally, traverse all control-flow paths and draw data-flow dependency edges ...
        Logger.info("\nAdding data-flow edges ... ");
        for (int i = 0; i < files.length; ++i) {
            addDataFlowEdges(cfgs[i], ddgs[i]);
            ddgs[i].attachCFG(cfgs[i]);
        }
        Logger.info("Done.\n");

        return ddgs;
    }

    // 为每一个源代码文件都进行DDG分析
    private static void build(File file, ParseTree parseTree,
                              DataDependenceGraph ddg,
                              Map<ParserRuleContext, Object> pdNodes) throws IOException {
        // Extract the information of all given Cpp classes and NonClass Function
        Logger.info("\nExtracting class-infos and nonClass-Func-infos ... ");
        allClassInfos = new HashMap<>();
        allNonClassFunctionInfos = new HashMap<>();
        methodDEFs = new HashMap<>();
        List<JavaScriptClass> classesList = new LinkedList<>();
        List<JavaScriptMethod> functionsList = new LinkedList<>();

        JavaScriptClassExtractor.extractInfo(file.getPath(), parseTree, classesList, functionsList);
        for (JavaScriptClass cls : classesList)
            allClassInfos.put(cls.NAME, cls);
        Logger.info("Done.");

        // Initialize method DEF information
        Logger.info("\nInitializing method-DEF infos ... ");
        // 增加非class函数信息
        for (JavaScriptMethod func : functionsList) {
            List<MethodDefInfo> list = methodDEFs.get(func.NAME);
            if (list == null) {
                list = new ArrayList<>();
                list.add(new MethodDefInfo(func.RET_TYPE, func.NAME, null));
                methodDEFs.put(func.NAME, list);
            } else
                list.add(new MethodDefInfo(func.RET_TYPE, func.NAME, null));
        }
        // 增加class 成员函数信息
        for (JavaScriptClass cls : classesList)
            for (JavaScriptMethod func : cls.getAllMethods()) {
                List<MethodDefInfo> list = methodDEFs.get(func.NAME);
                if (list == null) {
                    list = new ArrayList<>();
                    list.add(new MethodDefInfo(func.RET_TYPE, func.NAME, cls.NAME));
                    methodDEFs.put(func.NAME, list);
                } else
                    list.add(new MethodDefInfo(func.RET_TYPE, func.NAME, cls.NAME));
            }
        Logger.info("Done.");

        Logger.info("\nIterative DEF-USE analysis ... ");
        boolean changed;
        int iteration = 0;
        do {
            ++iteration;
            changed = false;

            currentFile = file.getName();
            DefUseVisitor defUse = new DefUseVisitor(iteration, classesList.toArray(new JavaScriptClass[classesList.size()]), ddg, pdNodes);
            defUse.visit(parseTree);
            changed |= defUse.changed;

            Logger.debug("Iteration #" + iteration + ": " + (changed ? "CHANGED" : "NO-CHANGE"));
            Logger.debug("\n========================================\n");
        } while (changed);
        Logger.info("Done.");

    }

//    /**
//     * Analyze method DEF information for imported libraries.
//     */
//    //TODO
//    private static void analyzeImportsDEF(List<JavaScriptClass[]> filesClasses) throws IOException {
//        // Extract the import strings
//        Logger.info("\nExtracting & Parsing imports ... ");
//        Set<String> rawImports = new LinkedHashSet<>();
////        rawImports.add("java.lang.*");
//        for (JavaScriptClass[] classes : filesClasses)
//            for (JavaScriptClass cls : classes)
//                for (String qualifiedName : cls.IMPORTS)
//                    rawImports.add(qualifiedName);
//        // NOTE: imports can contain specific or whole package imports;
//        //       so, we need to extract specific ZIP-entries for all imports.
//        // For each import find the ZIP-entries,
//        // and extract the ParseTree and JavaClass[] infos
//        ZipFile zip = new ZipFile("res/jdk7-src.zip");
//        Set<String> imports = new LinkedHashSet<>();
//        List<ParseTree> importsParseTrees = new ArrayList<>();
//        List<JavaScriptClass[]> importsClassInfos = new ArrayList<>();
//        for (String qualifiedName : rawImports) {
//            if (qualifiedName.endsWith(".*")) {
//                for (ZipEntry ent : getPackageEntries(zip, qualifiedName)) {
//                    if (imports.add(ent.getName())) {
//                        ANTLRInputStream input = new ANTLRInputStream(zip.getInputStream(ent));
//                        JavaScriptLexer lexer = new JavaScriptLexer(input);
//                        CommonTokenStream tokens = new CommonTokenStream(lexer);
//                        JavaScriptParser parser = new JavaScriptParser(tokens);
//                        ParseTree tree = parser.program();
//                        //
//                        importsParseTrees.add(tree);
//                        List<JavaScriptClass> list = JavaScriptClassExtractor.extractInfo("src.zip/" + ent.getName(), tree);
//                        importsClassInfos.add(list.toArray(new JavaScriptClass[list.size()]));
//                        for (JavaScriptClass cls : list)
//                            allClassInfos.put(cls.NAME, cls);
//                    }
//                }
//            } else {
//                String path = qualifiedName.replace('.', '/') + ".java";
//                if (imports.add(path)) {
//                    ZipEntry entry = zip.getEntry(path);
//                    if (entry == null) {
//                        imports.remove(path);
//                        continue;
//                    }
//                    //
//                    ANTLRInputStream input = new ANTLRInputStream(zip.getInputStream(entry));
//                    JavaScriptLexer lexer = new JavaScriptLexer(input);
//                    CommonTokenStream tokens = new CommonTokenStream(lexer);
//                    JavaScriptParser parser = new JavaScriptParser(tokens);
//                    ParseTree tree = parser.program();
//                    //
//                    importsParseTrees.add(tree);
//                    List<JavaScriptClass> list = JavaScriptClassExtractor.extractInfo("src.zip/" + path, tree);
//                    importsClassInfos.add(list.toArray(new JavaScriptClass[list.size()]));
//                }
//            }
//        }
//        Logger.info("Done.");
//        //
//        for (JavaScriptClass[] classArray : importsClassInfos) {
//            for (JavaScriptClass cls : classArray) {
//                for (JavaScriptMethod mtd : cls.getAllMethods()) {
//                    List<MethodDefInfo> list = methodDEFs.get(mtd.NAME);
//                    if (list == null) {
//                        list = new ArrayList<>();
//                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
//                        methodDEFs.put(mtd.NAME, list);
//                    } else {
//                        list.add(new MethodDefInfo(mtd.RET_TYPE, mtd.NAME, cls.PACKAGE, cls.NAME, mtd.ARG_TYPES));
//                        // no need to do 'methodDEFs.put(...)' again
//                    }
//                }
//            }
//        }
//        //
//        Logger.info("\nAnalyzing imports DEF-USE ... ");
//        Map<ParserRuleContext, Object> dummyMap = new HashMap<>();
//        DataDependenceGraph dummyDDG = new DataDependenceGraph("Dummy.java");
//        boolean changed;
//        int iteration = 0;
//        do {
//            ++iteration;
//            changed = false;
//            int i = 0;
//            for (String imprt : imports) {
//                currentFile = "src.zip/" + imprt;
//                JavaScriptDDGBuilder.DefUseVisitor defUse = new JavaScriptDDGBuilder.DefUseVisitor(iteration, importsClassInfos.get(i), dummyDDG, dummyMap);
//                defUse.visit(importsParseTrees.get(i));
//                changed |= defUse.changed;
//                ++i;
//            }
//        } while (changed);
//        Logger.info("Done.");
//        //
//        dummyDDG = null;
//        dummyMap.clear();
//    }

//    // todo 没啥用？JS没有包的概念
//    /**
//     * Returns an array of ZipEntry for a given wildcard package import.
//     */
//    private static ZipEntry[] getPackageEntries(ZipFile zip, String qualifiedName) {
//        // qualifiedName ends with ".*"
//        String pkg = qualifiedName.replace('.', '/').substring(0, qualifiedName.length() - 1);
//        int slashCount = countSlashes(pkg);
//        ArrayList<ZipEntry> entries = new ArrayList<>();
//        Enumeration<? extends ZipEntry> zipEntries = zip.entries();
//        while (zipEntries.hasMoreElements()) {
//            ZipEntry entry = zipEntries.nextElement();
//            if (entry.getName().startsWith(pkg)
//                    && !entry.isDirectory()
//                    && slashCount == countSlashes(entry.getName())) {
//                entries.add(entry);
//            }
//        }
//        return entries.toArray(new ZipEntry[entries.size()]);
//    }

    /**
     * Returns the number of forward-slash ('/') characters in a given string.
     */
    private static int countSlashes(String str) {
        int slashCount = 0;
        for (char chr : str.toCharArray())
            if (chr == '/')
                ++slashCount;
        return slashCount;
    }

    // 算法：传入cfg和ddg，将ddg的边找出来
    /**
     * Traverses each CFG and uses the extracted DEF-USE info
     * to add Flow-dependence edges to the corresponding DDG.
     */
    private static void addDataFlowEdges(ControlFlowGraph cfg, DataDependenceGraph ddg) {
        Set<CFNode> visitedDefs = new LinkedHashSet<>();
        for (CFNode entry : cfg.getAllMethodEntries()) {
            visitedDefs.clear();
            CFPathTraversal defTraversal = new CFPathTraversal(cfg, entry);
            while (defTraversal.hasNext()) {
                CFNode defCFNode = defTraversal.next();
                if (!visitedDefs.add(defCFNode)) {
                    defTraversal.continueNextPath();
                    continue;
                }
                PDNode defNode = (PDNode) defCFNode.getProperty("pdnode");
                if (defNode == null) {
                    //Logger.debug("No PDNode: " + defCFNode);
                    continue;
                }
                if (defNode.getAllDEFs().length == 0)
                    continue;
                // first add any self-flows of this node
                for (String flow : defNode.getAllSelfFlows()) {
                    ddg.addEdge(new Edge<>(defNode, new DDEdge(DDEdge.Type.FLOW, flow), defNode));
                }
                // now traverse the CFG for any USEs till a DEF
                Set<CFNode> visitedUses = new LinkedHashSet<>();
                for (String def : defNode.getAllDEFs()) {
                    CFPathTraversal useTraversal = new CFPathTraversal(cfg, defCFNode);
                    visitedUses.clear();
                    CFNode useCFNode = useTraversal.next(); // skip start node
                    visitedUses.add(useCFNode);
                    while (useTraversal.hasNext()) {
                        useCFNode = useTraversal.next();
                        PDNode useNode = (PDNode) useCFNode.getProperty("pdnode");
                        if (useNode == null) {
                            //Logger.debug("No PDNode: " + useCFNode);
                            continue;
                        }
                        if (useNode.hasDEF(def))
                            useTraversal.continueNextPath(); // no need to continue this path
                        if (!visitedUses.add(useCFNode))
                            useTraversal.continueNextPath(); // no need to continue this path
                        else if (useNode.hasUSE(def))
                            ddg.addEdge(new Edge<>(defNode, new DDEdge(DDEdge.Type.FLOW, def), useNode));
                    }
                }
            }
        }
    }


    /**
     * Visitor class which performs iterative DEF-USE analysis for all program statements.
     */
    private static class DefUseVisitor extends JavaScriptBaseVisitor<String> {

        private static final int PARAM = 1;
        private static final int FIELD = 101;
        private static final int LOCAL = 202;
        private static final int OUTER = 303;

        private int iteration;
        private boolean changed;
        private boolean analysisVisit;
        private JavaScriptClass[] classInfos;
        private DataDependenceGraph ddg;
        private Set<String> defList, useList, selfFlowList;
        private Map<ParserRuleContext, Object> pdNodes;
        private Deque<JavaScriptClass> activeClasses;
        private MethodDefInfo methodDefInfo;
        private JavaScriptField[] methodParams;
        private List<JavaScriptField> localVars;

        public DefUseVisitor(int iter, JavaScriptClass[] classInfos,
                             DataDependenceGraph ddg, Map<ParserRuleContext, Object> pdNodes) {
            Logger.debug("FILE IS: " + currentFile);
            this.ddg = ddg;
            changed = false;
            iteration = iter;
            analysisVisit = false;
            this.pdNodes = pdNodes;
            this.classInfos = classInfos;
            defList = new LinkedHashSet<>();
            useList = new LinkedHashSet<>();
            selfFlowList = new LinkedHashSet<>();
            activeClasses = new ArrayDeque<>();
            methodDefInfo = null;
            methodParams = new JavaScriptField[0];
            localVars = new ArrayList<>();
        }

        /**
         * 分析def-use
         * @param node
         * @param expression
         */
        private void analyseDefUse(PDNode node, ParseTree expression) {
            Logger.debug("--- ANALYSIS ---");
            Logger.debug(node.toString());
            analysisVisit = true;
            String expr = visit(expression);
            Logger.debug(expr);
            //
            StringBuilder locVarsStr = new StringBuilder(256);
            locVarsStr.append("LOCAL VARS = [");
            for (JavaScriptField lv : localVars)
                locVarsStr.append(' ').append(lv.NAME).append(", ");
            locVarsStr.append("]");
            Logger.debug(locVarsStr.toString());
            //
            if (isUsableExpression(expr)) {
                useList.add(expr);
                Logger.debug("USABLE");
            }
            analysisVisit = false;
            Logger.debug("Changed = " + changed);
            Logger.debug("DEFs = " + Arrays.toString(node.getAllDEFs()));
            Logger.debug("USEs = " + Arrays.toString(node.getAllUSEs()));

            // todo 主要：将def信息更新
            // todo 判断xx类型，可以自己改
            for (String def : defList) {
                int status = isDefined(def);
                if (status > -1) {
                    if (status < 100) {
                        methodDefInfo.setArgDEF(status, true);
                        Logger.debug("Method defines argument #" + status);
                    } else if (status == FIELD) {
                        methodDefInfo.setStateDEF(true);
                        // todo this.的话直接提取第五个字符后面的变量，然后加进去
                        if (def.startsWith("this."))
                            def = def.substring(5);
                        def = "$THIS." + def;
                        Logger.debug("Method defines object state.");
                    }
                    changed |= node.addDEF(def);
                } else
                    Logger.debug(def + " is not defined!");
            }
            Logger.debug("Changed = " + changed);
            Logger.debug("DEFs = " + Arrays.toString(node.getAllDEFs()));

            // todo 主要：将use信息更新
            for (String use : useList) {
                int status = isDefined(use);
                if (status > -1) {
                    if (status == FIELD) {
                        if (use.startsWith("this."))
                            use = use.substring(5);
                        use = "$THIS." + use;
                    }
                    changed |= node.addUSE(use);
                } else
                    Logger.debug(use + " is not defined!");
            }
            Logger.debug("Changed = " + changed);
            Logger.debug("USEs = " + Arrays.toString(node.getAllUSEs()));

            // todo 自己对象的变量？
            for (String flow : selfFlowList) {
                int status = isDefined(flow);
                if (status > -1) {
                    if (status == FIELD) {
                        if (flow.startsWith("this."))
                            flow = flow.substring(5);
                        flow = "$THIS." + flow;
                    }
                    changed |= node.addSelfFlow(flow);
                } else
                    Logger.debug(flow + " is not defined!");
            }
            Logger.debug("Changed = " + changed);
            Logger.debug("SELF_FLOWS = " + Arrays.toString(node.getAllSelfFlows()));
            defList.clear();
            useList.clear();
            selfFlowList.clear();
            Logger.debug("----------------");
        }

        /**
         * Check if a given symbol is a defined variable.
         * This returns -1 if the symbol is not defined; otherwise,
         * it returns 101 if the symbol is a class field,
         * or returns 202 if the symbol is a local variable,
         * or returns 303 if the symbol is an outer class field,
         * or if the symbol is a method parameter, returns the index of the parameter.
         */
        private int isDefined(String id) {
            for (int i = 0; i < methodParams.length; ++i)
                if (methodParams[i].NAME.equals(id))
                    return i;
            for (JavaScriptField local : localVars)
                if (local.NAME.equals(id))
                    return LOCAL;
            if (id.startsWith("this."))
                id = id.substring(5);
            if (!activeClasses.isEmpty()) {
                for (JavaScriptField field : activeClasses.peek().getAllFields())
                    if (field.NAME.equals(id))
                        return FIELD;
                for (JavaScriptClass cls : activeClasses)
                    for (JavaScriptField field : cls.getAllFields())
                        if (field.NAME.equals(id))
                            return OUTER;
            }

            return -1;
        }

        // todo 可能要改，因为js没type
        /**
         * Return type of a given symbol.
         * Returns null if symbol is not found.
         */
//        private String getType(String id) {
//            if (isUsableExpression(id)) {
//                for (JavaScriptField param : methodParams)
//                    if (param.NAME.equals(id))
//                        return param.TYPE;
//                for (JavaScriptField local : localVars)
//                    if (local.NAME.equals(id))
//                        return local.TYPE;
//                if (id.startsWith("this."))
//                    id = id.substring(4);
//                for (JavaScriptField field : activeClasses.peek().getAllFields())
//                    if (field.NAME.equals(id))
//                        return field.TYPE;
//                for (JavaScriptClass cls : activeClasses)
//                    for (JavaScriptField field : cls.getAllFields())
//                        if (field.NAME.equals(id))
//                            return field.TYPE;
//                Logger.debug("getType(" + id + ") : is USABLE but NOT DEFINED");
//                return null;
//            } else {
//                Logger.debug("getType(" + id + ") : is NOT USABLE");
//                // might be:
//                // 'this'
//                // 'super'
//                // literal ($INT, $DBL, $CHR, $STR, $BOL)
//                // class-name  [ ID ]
//                // constructor-call [ $NEW creator ]
//                // method-call [ expr(exprList) ]
//                // casting [ $CAST(type) expr ]
//                // array-indexing  [ expr[expr] ]
//                // unary-op [ ++, --, !, ~ ]
//                // paren-expr [ (...) ]
//                // array-init [ {...} ]
//                return null;
//            }
//        }

        private JavaScriptClass findClass(String type) {
            return null;
        }

        // 没有实际作用，用来找类的信息提取，根据name返回methodInfo，对实际的提取没太大作用
        /**
         * Find and return matching method-definition-info.
         * Returns null if not found.
         */
        private MethodDefInfo findDefInfo(String callee, String name, JavaScriptParser.ArgumentsContext ctx) {
            List<MethodDefInfo> list = methodDEFs.get(name);
            Logger.debug("METHOD NAME: " + name);
            Logger.debug("# found = " + (list == null ? 0 : list.size()));
            //
            if (list == null)
                return null;
            //
            if (list.size() == 1) { // only one candidate
                Logger.debug("SINGLE CANDIDATE");
                MethodDefInfo mtd = list.get(0);
                Logger.debug("WITH MATCHING PARAMS COUNT");
                return mtd;
            }
            //
            if (callee == null) { // no callee; so search for self methods
                Logger.debug("NO CALLEE");
                forEachDefInfo:
                for (MethodDefInfo mtd : list) {
                    // check class-name
                    boolean classNameMatch = false;
                    for (JavaScriptClass cls : activeClasses) {
                        if (mtd.CLASS_NAME.equals(cls.NAME)) {
                            classNameMatch = true;
                            break;
                        }
                    }
                    if (!classNameMatch)
                        continue;
                    // no param count to check
                    // no param type to check
                    return mtd;
                }
            } else if (isDefined(callee) > -1) { // has a defined callee
                Logger.debug("DEFINED CALLEE");
//                String type = getType(callee);
                JavaScriptClass cls = allClassInfos.get(name);
                if (cls != null && cls.hasMethod(name)) {
                    forEachDefInfo:
                    for (MethodDefInfo mtd : list) {
                        if (!mtd.CLASS_NAME.equals(cls.NAME))
                            continue;
                        return mtd;
                    }
                    Logger.debug("METHOD DEF INFO NOT FOUND!");
                } else {
                    Logger.debug((cls == null ?
                            " NOT FOUND!" :
                            "CLASS HAS NO SUCH METHOD!"));
                }
            } else { // has an undefined callee
                Logger.debug("UNDEFINED CALLEE.");
                //
                // TODO: use a global retType for visiting expressions
                //
            }
            return null;
        }

        /**
         * Find and return matching method-definition-info.
         * Returns null if not found.
         */
        private MethodDefInfo findDefInfo(String name, String type) {
            List<MethodDefInfo> infoList = methodDEFs.get(name);
            if (infoList.size() > 1) {
                forEachInfo:
                for (MethodDefInfo info : infoList) {
                    if (!info.CLASS_NAME.equals(activeClasses.peek().NAME))
                        continue;
                    if ((info.RET_TYPE == null && type != null) ||
                            (info.RET_TYPE != null && type == null))
                        continue;
                    if (type != null && !type.startsWith(info.RET_TYPE))
                        continue;
                    return info;
                }
            } else if (infoList.size() == 1)
                return infoList.get(0);
            return null;
        }

        /**************************************
         **************************************
         ***          DECLARATIONS          ***
         **************************************
         **************************************/

        @Override
        public String visitClassDeclaration(JavaScriptParser.ClassDeclarationContext ctx) {
            //classDeclaration
            //    : Class identifier classTail
            //    ;
            for (JavaScriptClass cls : classInfos) {
                if (cls.NAME.equals(ctx.identifier().Identifier().getText())) {
                    // todo 一些基本的判断，跟文法相关
                    activeClasses.push(cls);
                    visit(ctx.classTail());
                    activeClasses.pop();
                    break;
                }
            }
            return null;
        }

        //TODO
        @Override
        public String visitClassTail(JavaScriptParser.ClassTailContext ctx) {
            //    : (Extends singleExpression)? '{' classElement* '}'
            //classElement
            //    : (Static | {this.n("static")}? identifier | Async)* (methodDefinition | assignable '=' objectLiteral ';')
            //    | emptyStatement_
            //    | '#'? propertyName '=' singleExpression
            //    ;

            // old
//            localVars.clear();
//            for (JavaScriptParser.ClassElementContext classElement : ctx.classElement()) {
//                if (classElement.methodDefinition() != null) {
//                    methodParams = new JavaScriptField[0];
//                    methodDefInfo = new MethodDefInfo(null, "block", "", activeClasses.peek().NAME, null);
//                    return null;
//                }
//            }

            // new
            for (JavaScriptParser.ClassElementContext classElementContext : ctx.classElement()) {
                visit(classElementContext);
            }
            return null;
        }


        @Override
        public String visitMethodDefinition(JavaScriptParser.MethodDefinitionContext ctx) {
            //methodDefinition
            //    : '*'? '#'? propertyName '(' formalParameterList? ')' functionBody
            //    | '*'? '#'? getter '(' ')' functionBody
            //    | '*'? '#'? setter '(' formalParameterList? ')' functionBody
            //    ;
            PDNode entry;
            if (iteration == 1) {
                // 第一次迭代
                // 创建方法入口，设置代码行号和内容
                // 注意要和cfg的行号和内容一模一样
                entry = new PDNode();
                entry.setLineOfCode(ctx.getStart().getLine());
                String args = "";
                if (ctx.propertyName() != null) {
                    args = ctx.formalParameterList() == null ? "" : getOriginalCodeText(ctx.formalParameterList());
                    entry.setCode(ctx.propertyName().getText() + "(" + args + ")");
                    entry.setProperty("name", ctx.propertyName().getText());
                } else if (ctx.getter() != null) {
                    entry.setCode(ctx.getter().getText() + "()");
                    entry.setProperty("name", ctx.getter().getText());
                } else {
                    args = getOriginalCodeText(ctx.formalParameterList());
                    entry.setCode(ctx.setter().getText() + "(" + args + ")");
                    entry.setProperty("name", ctx.setter().getText());
                }
                ddg.addVertex(entry);
                pdNodes.put(ctx, entry);
                //
                // Extract all parameter types and IDs
                List<String> paramIDs = new ArrayList<>();
                // js中参数声明和定义时不需要类型，因此不需要paramTypes
//                List<String> paramTypes = new ArrayList<>();
                if (ctx.formalParameterList() != null) {
                    // 若参数列表不为空
                    for (JavaScriptParser.FormalParameterArgContext prm : ctx.formalParameterList().formalParameterArg()) {
                        paramIDs.add(prm.assignable().getText());
                    }
                    // todo 为何单独处理lastParam
                    JavaScriptParser.LastFormalParameterArgContext lastParam = ctx.formalParameterList().lastFormalParameterArg();
                    if (lastParam != null) {
                        paramIDs.add(lastParam.singleExpression().getText());
                    }
                }

                // todo 第一次迭代即为def，整到defInfo里
                methodParams = new JavaScriptField[paramIDs.size()];
                for (int i = 0; i < methodParams.length; ++i)
                    methodParams[i] = new JavaScriptField(null, false, paramIDs.get(i));
                // 为这个方法的入口设置方法参数这个属性
                entry.setProperty("params", methodParams);
                //
                // Add initial DEF info: method entry nodes define the input-parameters
                for (String pid : paramIDs)
                    changed |= entry.addDEF(pid);
            } else {
                entry = (PDNode) pdNodes.get(ctx);
                methodParams = (JavaScriptField[]) entry.getProperty("params");
            }

            // todo 打印些信息
//            methodDefInfo = findDefInfo((String) entry.getProperty("name"),
//                    (String) entry.getProperty("type"));
//            if (methodDefInfo == null) {
//                Logger.error("Method NOT FOUND!");
//                Logger.error("NAME = " + (String) entry.getProperty("name"));
//                Logger.error("TYPE = " + (String) entry.getProperty("type"));
//                Logger.error("PARAMS = " + Arrays.toString(methodParams));
//                Logger.error("CLASS = " + activeClasses.peek().NAME);
////                Logger.error("PACKAGE = " + activeClasses.peek().PACKAGE);
//                List list = methodDEFs.get((String) entry.getProperty("name"));
//                for (int i = 0; i < list.size(); ++i)
//                    Logger.error(list.get(i).toString());
//            }

            // Now visit method body ...
            localVars.clear();
            if (ctx.functionBody() != null)
                visit(ctx.functionBody());
            //
            localVars.clear();
            methodParams = new JavaScriptField[0];
            return null;
        }

        @Override
        public String visitVariableStatement(JavaScriptParser.VariableStatementContext ctx) {
//            variableStatement:
//                variableDeclarationList eos
//                ;
//            variableDeclarationList
//                : varModifier variableDeclaration (',' variableDeclaration)*
//                ;
//
//            variableDeclaration
//                : assignable ('=' singleExpression)? // ECMAScript 6: Array & Object Matching
//                ;

            // 将每个声明的局部变量记录下来
            for (JavaScriptParser.VariableDeclarationContext variableDeclarationContext : ctx.variableDeclarationList().variableDeclaration()) {
                localVars.add(new JavaScriptField(ctx.variableDeclarationList().varModifier().getText(), false, variableDeclarationContext.assignable().getText()));
            }

            if (analysisVisit)
                return visit(ctx.variableDeclarationList());

            PDNode declr;
            if (iteration == 1) {
                // 第一次迭代就是def，创建节点，要与cfg的一样
                declr = new PDNode();
                declr.setLineOfCode(ctx.getStart().getLine());
                declr.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(declr);
                pdNodes.put(ctx, declr);
            } else // 后续遍历直接取用
                declr = (PDNode) pdNodes.get(ctx);

            // 丢进去分析
            analyseDefUse(declr, ctx.variableDeclarationList());
            return null;
        }

        @Override
        public String visitVariableDeclarationList(JavaScriptParser.VariableDeclarationListContext ctx) {
//            variableDeclarationList
//                : varModifier variableDeclaration (',' variableDeclaration)*
//                ;
//
//            variableDeclaration
//                : assignable ('=' singleExpression)? // ECMAScript 6: Array & Object Matching
//                ;
            StringBuilder vars = new StringBuilder();
            vars.append(visit(ctx.variableDeclaration(0)));
            for (int i = 1; i < ctx.variableDeclaration().size(); i++)
                vars.append(", ").append(visit(ctx.variableDeclaration(i)));

            return vars.toString();
        }

        @Override
        public String visitVariableDeclaration(JavaScriptParser.VariableDeclarationContext ctx) {
//            variableDeclarationList
//                : varModifier variableDeclaration (',' variableDeclaration)*
//                ;
//
//            variableDeclaration
//                : assignable ('=' singleExpression)? // ECMAScript 6: Array & Object Matching
//                ;

            String init = "";
            String varID = ctx.assignable().getText();
            if (ctx.singleExpression() != null) {
                // todo 想办法实现ctx.singleExpression()，可以看看python这里怎么操作
                init = visitSingleExpression(ctx.singleExpression());
                if (isUsableExpression(init))
                    useList.add(init);
                defList.add(varID);
                init = " $INIT " + init;
            }
            return "$VAR " + varID + init;
        }

        /**
         * {@inheritDoc}
         *
         * <p>The default implementation returns the result of calling
         * {@link #visitChildren} on {@code ctx}.</p>
         */
        @Override
        public String visitFormalParameterList(JavaScriptParser.FormalParameterListContext ctx) {
            return visitChildren(ctx);
        }

        /**
         * {@inheritDoc}
         *
         * <p>The default implementation returns the result of calling
         * {@link #visitChildren} on {@code ctx}.</p>
         */
        @Override
        public String visitFormalParameterArg(JavaScriptParser.FormalParameterArgContext ctx) {
            return visitChildren(ctx);
        }

        /**
         * {@inheritDoc}
         *
         * <p>The default implementation returns the result of calling
         * {@link #visitChildren} on {@code ctx}.</p>
         */
        @Override
        public String visitLastFormalParameterArg(JavaScriptParser.LastFormalParameterArgContext ctx) {
            return visitChildren(ctx);
        }

        @Override
        public String visitFunctionDeclaration(JavaScriptParser.FunctionDeclarationContext ctx) {
//            functionDeclaration
//                : Async? Function_ '*'? identifier '(' formalParameterList? ')' functionBody
//                ;

            PDNode entry;
            if (iteration == 1) {
                // 第一次迭代
                // 创建方法入口，设置代码行号和内容
                // 注意要和cfg的行号和内容一模一样
                entry = new PDNode();

                StringBuilder t = new StringBuilder();
                for (int i = 0; i < ctx.getChildCount(); i++) {
//                System.out.println("token " + i + ": " + ctx.getChild(i).getText() + " " + ctx.getChild(i).getClass());
                    if (ctx.getChild(i).getText().equals("*"))
                        t.append("*");
                }

                entry.setLineOfCode(ctx.getStart().getLine());
                String args = "";
                if (ctx.identifier() != null) {
                    // 若函数有参数则配置
                    if (ctx.formalParameterList() != null && !ctx.formalParameterList().isEmpty()) {
                        args = getOriginalCodeText(ctx.formalParameterList());
                    }
                    // 函数的入口为 修饰符 + 形参列表
                    entry.setCode("function" + t + ": " + ctx.identifier().Identifier() + "(" + args + ")");
                    entry.setProperty("name", ctx.identifier().getText());
                    ddg.addVertex(entry);
                    pdNodes.put(ctx, entry);
                }
                // todo 匿名函数

                // Extract all parameter types and IDs
                List<String> paramIDs = new ArrayList<>();
                // js中参数声明和定义时不需要类型，因此不需要paramTypes
                List<String> paramTypes = new ArrayList<>();
                if (ctx.formalParameterList() != null) {
                    // 若参数列表不为空
                    for (JavaScriptParser.FormalParameterArgContext prm : ctx.formalParameterList().formalParameterArg()) {
                        paramIDs.add(prm.assignable().getText());
                    }
                    // todo 为何单独处理lastParam
                    JavaScriptParser.LastFormalParameterArgContext lastParam = ctx.formalParameterList().lastFormalParameterArg();
                    if (lastParam != null) {
                        paramIDs.add(lastParam.singleExpression().getText());
                    }
                }

                // todo 第一次迭代即为def，整到defInfo里
                methodParams = new JavaScriptField[paramIDs.size()];
                for (int i = 0; i < methodParams.length; ++i)
                    methodParams[i] = new JavaScriptField(null, false, paramIDs.get(i));
                // 为这个方法的入口设置方法参数这个属性
                entry.setProperty("params", methodParams);
                //
                // Add initial DEF info: method entry nodes define the input-parameters
                for (String pid : paramIDs)
                    changed |= entry.addDEF(pid);
            } else {
                entry = (PDNode) pdNodes.get(ctx);
                methodParams = (JavaScriptField[]) entry.getProperty("params");
            }

            // 打印些信息
//            methodDefInfo = findDefInfo((String) entry.getProperty("name"),
//                    (String) entry.getProperty("type"));
//            if (methodDefInfo == null) {
//                Logger.error("Method NOT FOUND!");
//                Logger.error("NAME = " + (String) entry.getProperty("name"));
//                Logger.error("TYPE = " + (String) entry.getProperty("type"));
//                Logger.error("PARAMS = " + Arrays.toString(methodParams));
//                Logger.error("CLASS = " + activeClasses.peek().NAME);
//                Logger.error("PACKAGE = " + activeClasses.peek().PACKAGE);
//                List list = methodDEFs.get((String) entry.getProperty("name"));
//                for (int i = 0; i < list.size(); ++i)
//                    Logger.error(list.get(i).toString());
//            }

            // Now visit method body ...
            localVars.clear();
            if (ctx.functionBody() != null)
                visit(ctx.functionBody());
            //
            localVars.clear();
            methodParams = new JavaScriptField[0];
            return null;
        }

        /**
         * {@inheritDoc}
         *
         * <p>The default implementation returns the result of calling
         * {@link #visitChildren} on {@code ctx}.</p>
         */
        @Override
        public String visitFunctionBody(JavaScriptParser.FunctionBodyContext ctx) {
//            functionDeclaration
//                : Async? Function_ '*'? identifier '(' formalParameterList? ')' functionBody
//                ;
            return visitChildren(ctx);
        }


        @Override
        public String visitExpressionSequence(JavaScriptParser.ExpressionSequenceContext ctx) {
            //expressionSequence
            //    : singleExpression (',' singleExpression)*
            //    ;

            StringBuilder expList = new StringBuilder(visit(ctx.singleExpression(0)));
            for (int i = 1; i < ctx.singleExpression().size(); ++i)
                expList.append(", ").append(visit(ctx.singleExpression(i)));
            return expList.toString();
        }

        public String visitSingleExpression(JavaScriptParser.SingleExpressionContext ctx) {
            if (ctx.getText().startsWith("(") && ctx.getText().endsWith(")"))
                return '(' + visit(ctx) + ')';
            if (ctx.getText().equals("this"))
                return "this";
            if (ctx.getText().equals("super"))
                return "super";
//            if (ctx.Identifier() != null)
//                return ctx.Identifier().getText();
            if (ctx.getText().endsWith(".class"))
                return "$CLS";
            //
            return ctx.getText();
        }

        /************************************
         ************************************
         ***          STATEMENTS          ***
         ************************************
         ************************************/


        @Override
        public String visitExpressionStatement(JavaScriptParser.ExpressionStatementContext ctx) {
            //expressionStatement
            //    : {this.notOpenBraceAndNotFunction()}? expressionSequence eos
            //    ;
            if (analysisVisit)
                return visit(ctx.expressionSequence());
            //
            PDNode expr;
            if (iteration == 1) {
                expr = new PDNode();
                expr.setLineOfCode(ctx.getStart().getLine());
                expr.setCode(getOriginalCodeText(ctx.expressionSequence()));
                ddg.addVertex(expr);
                pdNodes.put(ctx, expr);
            } else
                expr = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(expr, ctx.expressionSequence());
            return null;
        }

        @Override
        public String visitIfStatement(JavaScriptParser.IfStatementContext ctx) {
            //
//            ifStatement
//            : If '(' expressionSequence ')' statement (Else statement)?
//            ;
            PDNode ifNode;
            if (iteration == 1) {
                ifNode = new PDNode();
                ifNode.setLineOfCode(ctx.getStart().getLine());
                ifNode.setCode("if " + getOriginalCodeText(ctx.expressionSequence()));
                ddg.addVertex(ifNode);
                pdNodes.put(ctx, ifNode);
            } else
                ifNode = (PDNode) pdNodes.get(ctx);
            //
            for (JavaScriptParser.SingleExpressionContext expressionContext : ctx.expressionSequence().singleExpression()) {
                // Now analyse DEF-USE by visiting the expression ...
                analyseDefUse(ifNode, expressionContext);
            }
            //
            for (JavaScriptParser.StatementContext stmnt : ctx.statement())
                visit(stmnt);
            return null;
        }

        @Override
        public String visitDoStatement(JavaScriptParser.DoStatementContext ctx) {
            //     : Do statement While '(' expressionSequence ')' eos                                                                       # DoStatement
            visit(ctx.statement());
            //
            PDNode whileNode;
            if (iteration == 1) {
                whileNode = new PDNode();
                whileNode.setLineOfCode(ctx.expressionSequence().getStart().getLine());
                whileNode.setCode("while " + getOriginalCodeText(ctx.expressionSequence()));
                ddg.addVertex(whileNode);
                pdNodes.put(ctx, whileNode);
            } else
                whileNode = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.expressionSequence().singleExpression()) {
                analyseDefUse(whileNode, singleExpressionContext);
            }

            return null;
        }

        @Override
        public String visitWhileStatement(JavaScriptParser.WhileStatementContext ctx) {
            //     | While '(' expressionSequence ')' statement                                                                              # WhileStatement
            PDNode whileNode;
            if (iteration == 1) {
                whileNode = new PDNode();
                whileNode.setLineOfCode(ctx.getStart().getLine());
                whileNode.setCode("while " + getOriginalCodeText(ctx.expressionSequence()));
                ddg.addVertex(whileNode);
                pdNodes.put(ctx, whileNode);
            } else
                whileNode = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            for (JavaScriptParser.SingleExpressionContext singleExpression : ctx.expressionSequence().singleExpression()) {
                analyseDefUse(whileNode, singleExpression);
            }
            //
            return visit(ctx.statement());
        }

        /**
         * 经典for循环
         * @param ctx
         * @return
         */
        @Override
        public String visitForStatement(JavaScriptParser.ForStatementContext ctx) {
            //     | For '(' (expressionSequence | variableDeclarationList)? ';' expressionSequence? ';' expressionSequence? ')' statement   # ForStatement
            int entrySize = localVars.size();
            //  First, we should check type of for-loop ...
            // It's a traditional for-loop:
            int index = 0;
            // for-init
            if (ctx.expressionSequence().size() == 3) {
                // 初始化 循环条件 更新 都有
                ++index;
                PDNode forInit;
                if (iteration == 1) {
                    forInit = new PDNode();
                    forInit.setLineOfCode(ctx.expressionSequence(0).getStart().getLine());
                    forInit.setCode(getOriginalCodeText(ctx.expressionSequence(0)));
                    ddg.addVertex(forInit);
                    pdNodes.put(ctx.expressionSequence(0), forInit);
                } else
                    forInit = (PDNode) pdNodes.get(ctx.expressionSequence(0));
                //
                // Now analyse DEF-USE by visiting the expression ...
                if (ctx.variableDeclarationList() != null)
                    analyseDefUse(forInit, ctx.variableDeclarationList());
                else
                    analyseDefUse(forInit, ctx.expressionSequence(0));
            } else {
                //variableDeclarationList不为空
                if (ctx.variableDeclarationList() != null) {
                    PDNode forInit;

                    // 将每个声明的局部变量记录下来
                    for (JavaScriptParser.VariableDeclarationContext variableDeclarationContext : ctx.variableDeclarationList().variableDeclaration()) {
                        localVars.add(new JavaScriptField(ctx.variableDeclarationList().varModifier().getText(), false, variableDeclarationContext.assignable().getText()));
                    }

                    if (iteration == 1) {
                        forInit = new PDNode();
                        forInit.setLineOfCode(ctx.variableDeclarationList().getStart().getLine());
                        forInit.setCode(getOriginalCodeText(ctx.variableDeclarationList()));
                        ddg.addVertex(forInit);
                        pdNodes.put(ctx.variableDeclarationList(), forInit);
                    } else
                        forInit = (PDNode) pdNodes.get(ctx.variableDeclarationList());
                    //
                    // Now analyse DEF-USE by visiting the expression ...
                    analyseDefUse(forInit, ctx.variableDeclarationList());
                }
            }

            // for-expression
            if (ctx.expressionSequence(index) != null) { // non-empty predicate-expression
                PDNode forExpr;
                if (iteration == 1) {
                    forExpr = new PDNode();
                    forExpr.setLineOfCode(ctx.expressionSequence(index).getStart().getLine());
                    forExpr.setCode("for (" + getOriginalCodeText(ctx.expressionSequence(index)) + ")");
                    ddg.addVertex(forExpr);
                    pdNodes.put(ctx.expressionSequence(index), forExpr);
                } else
                    forExpr = (PDNode) pdNodes.get(ctx.expressionSequence(index));
                //
                // Now analyse DEF-USE by visiting the expression ...
                analyseDefUse(forExpr, ctx.expressionSequence(index));
                index++;
            }
            // for-update
            if (ctx.expressionSequence(index) != null) { // non-empty for-update
                PDNode forUpdate;
                if (iteration == 1) {
                    forUpdate = new PDNode();
                    forUpdate.setCode(getOriginalCodeText(ctx.expressionSequence(index)));
                    forUpdate.setLineOfCode(ctx.expressionSequence(index).getStart().getLine());
                    ddg.addVertex(forUpdate);
                    pdNodes.put(ctx.expressionSequence(index), forUpdate);
                } else
                    forUpdate = (PDNode) pdNodes.get(ctx.expressionSequence(index));
                //
                // Now analyse DEF-USE by visiting the expression ...
                analyseDefUse(forUpdate, ctx.expressionSequence(index));
            }
            // visit for loop body
            String visit = visit(ctx.statement());
            // clear any local vars defined in the for loop
            if (localVars.size() > entrySize)
                localVars.subList(entrySize, localVars.size()).clear();
            return visit;
        }

        @Override
        public String visitForInStatement(JavaScriptParser.ForInStatementContext ctx) {
            //    | For '(' (singleExpression | variableDeclarationList) In expressionSequence ')' statement                                # ForInStatement
            int entrySize = localVars.size();
            PDNode forExpr;
            if (iteration == 1) {
                forExpr = new PDNode();
                // 判断是单个表达式还是变量声明序列
                if (ctx.singleExpression() != null) {
                    // for ( var ForBinding in Expression) Statement
                    // 使用一个已声明的变量ForBinding，用于在循环中迭代对象的属性名，Expression 是要遍历的对象。
                    forExpr.setLineOfCode(ctx.statement().getStart().getLine());
                    forExpr.setCode("for(" + getOriginalCodeText(ctx.singleExpression()) + " in " + getOriginalCodeText(ctx.expressionSequence()) + ")");
                    pdNodes.put(ctx.singleExpression(), forExpr);
                } else {
                    // 将每个声明的局部变量记录下来
//                    for (JavaScriptParser.VariableDeclarationContext variableDeclarationContext : ctx.variableDeclarationList().variableDeclaration()) {
//                        localVars.add(new JavaScriptField(ctx.variableDeclarationList().varModifier().getText(), false, variableDeclarationContext.assignable().getText()));
//                    }

                    // for ( ForDeclaration in Expression) Statement
                    // 这种形式使用 var、let、const 声明了一个变量，用于在循环中迭代对象的属性名，Expression 是要遍历的对象。
                    forExpr.setLineOfCode(ctx.variableDeclarationList().getStart().getLine());

                    // 声明多个变量时为语法错误
                    if (ctx.variableDeclarationList().variableDeclaration().size() > 1)
                        forExpr.setCode("Syntax Error: for(" + getOriginalCodeText(ctx.variableDeclarationList()) + " in " + getOriginalCodeText(ctx.expressionSequence()) + ")");
                    else
                        forExpr.setCode("for(" + getOriginalCodeText(ctx.variableDeclarationList()) + " in " + getOriginalCodeText(ctx.expressionSequence()) + ")");
                    pdNodes.put(ctx.variableDeclarationList(), forExpr);
                }
                ddg.addVertex(forExpr);
            } else if (ctx.singleExpression() != null) {
                forExpr = (PDNode) pdNodes.get(ctx.singleExpression());
            } else {
                forExpr = (PDNode) pdNodes.get(ctx.variableDeclarationList());
            }

            // Now analyse DEF-USE by visiting the expression ...
            String var;
            // 存储临时变量
            if (ctx.singleExpression() != null) {
                // 若为单个表达式（即变量已被声明过）
                var = ctx.singleExpression().getChild(0).getText();
                System.out.println(getOriginalCodeText(ctx.singleExpression()));
                changed |= forExpr.addDEF(var);
            } else if (ctx.variableDeclarationList() != null) {
                // 若为变量声明（刚刚进行声明）
                var = ctx.variableDeclarationList().getChild(1).getText();
                System.out.println(getOriginalCodeText(ctx.variableDeclarationList()));
                localVars.add(new JavaScriptField(ctx.variableDeclarationList().varModifier().getText(), false, var));
                changed |= forExpr.addDEF(var);
            }

            analyseDefUse(forExpr, ctx.expressionSequence());

            // visit for loop body
            String visit = visit(ctx.statement());
            // clear any local vars defined in the for loop
            if (localVars.size() > entrySize)
                localVars.subList(entrySize, localVars.size()).clear();
            return visit;
        }

        @Override
        public String visitForOfStatement(JavaScriptParser.ForOfStatementContext ctx) {
            //    | For '(' (singleExpression | variableDeclarationList) Of expressionSequence ')' statement                                # ForInStatement
            int entrySize = localVars.size();
            PDNode forExpr;
            if (iteration == 1) {
                forExpr = new PDNode();
                // 判断是单个表达式还是变量声明序列
                if (ctx.singleExpression() != null) {
                    // for ( var ForBinding in Expression) Statement
                    // 使用一个已声明的变量ForBinding，用于在循环中迭代对象的属性名，Expression 是要遍历的对象。
                    forExpr.setLineOfCode(ctx.statement().getStart().getLine());
                    forExpr.setCode("for(" + getOriginalCodeText(ctx.singleExpression()) + " of " + getOriginalCodeText(ctx.expressionSequence()) + ")");
                    pdNodes.put(ctx.singleExpression(), forExpr);
                } else {
                    // 将每个声明的局部变量记录下来
//                    for (JavaScriptParser.VariableDeclarationContext variableDeclarationContext : ctx.variableDeclarationList().variableDeclaration()) {
//                        localVars.add(new JavaScriptField(ctx.variableDeclarationList().varModifier().getText(), false, variableDeclarationContext.assignable().getText()));
//                    }

                    // for ( ForDeclaration in Expression) Statement
                    // 这种形式使用 var、let、const 声明了一个变量，用于在循环中迭代对象的属性名，Expression 是要遍历的对象。
                    forExpr.setLineOfCode(ctx.variableDeclarationList().getStart().getLine());

                    // 声明多个变量时为语法错误
                    if (ctx.variableDeclarationList().variableDeclaration().size() > 1)
                        forExpr.setCode("Syntax Error: for(" + getOriginalCodeText(ctx.variableDeclarationList()) + " of " + getOriginalCodeText(ctx.expressionSequence()) + ")");
                    else
                        forExpr.setCode("for(" + getOriginalCodeText(ctx.variableDeclarationList()) + " of " + getOriginalCodeText(ctx.expressionSequence()) + ")");
                    pdNodes.put(ctx.variableDeclarationList(), forExpr);
                }
                ddg.addVertex(forExpr);
            } else if (ctx.singleExpression() != null) {
                forExpr = (PDNode) pdNodes.get(ctx.singleExpression());
            } else {
                forExpr = (PDNode) pdNodes.get(ctx.variableDeclarationList());
            }

            // Now analyse DEF-USE by visiting the expression ...
            String var;
            // 存储临时变量
            if (ctx.singleExpression() != null) {
                // 若为单个表达式（即变量已被声明过）
                var = ctx.singleExpression().getChild(0).getText();
                System.out.println(getOriginalCodeText(ctx.singleExpression()));
                changed |= forExpr.addDEF(var);
            } else if (ctx.variableDeclarationList() != null) {
                // 若为变量声明（刚刚进行声明）
                var = ctx.variableDeclarationList().getChild(1).getText();
                System.out.println(getOriginalCodeText(ctx.variableDeclarationList()));
                localVars.add(new JavaScriptField(ctx.variableDeclarationList().varModifier().getText(), false, var));
                changed |= forExpr.addDEF(var);
            }

            analyseDefUse(forExpr, ctx.expressionSequence());

            // visit for loop body
            String visit = visit(ctx.statement());
            // clear any local vars defined in the for loop
            if (localVars.size() > entrySize)
                localVars.subList(entrySize, localVars.size()).clear();
            return visit;
        }

        @Override
        public String visitContinueStatement(JavaScriptParser.ContinueStatementContext ctx) {
            //continueStatement
            //    : Continue ({this.notLineTerminator()}? identifier)? eos
            //    ;
            PDNode ret;
            if (iteration == 1) {
                ret = new PDNode();
                ret.setLineOfCode(ctx.getStart().getLine());
                ret.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(ret);
                pdNodes.put(ctx, ret);
            } else
                ret = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            if (ctx.identifier() != null)
                analyseDefUse(ret, ctx.identifier());
            return null;
        }

        @Override
        public String visitBreakStatement(JavaScriptParser.BreakStatementContext ctx) {
            //breakStatement
            //    : Break ({this.notLineTerminator()}? identifier)? eos
            //    ;
            PDNode ret;
            if (iteration == 1) {
                ret = new PDNode();
                ret.setLineOfCode(ctx.getStart().getLine());
                ret.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(ret);
                pdNodes.put(ctx, ret);
            } else
                ret = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            if (ctx.identifier() != null)
                analyseDefUse(ret, ctx.identifier());
            return null;
        }

        @Override
        public String visitReturnStatement(JavaScriptParser.ReturnStatementContext ctx) {
            //returnStatement
            //    : Return ({this.notLineTerminator()}? expressionSequence)? eos
            //    ;
            PDNode ret;
            if (iteration == 1) {
                ret = new PDNode();
                ret.setLineOfCode(ctx.getStart().getLine());
                ret.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(ret);
                pdNodes.put(ctx, ret);
            } else
                ret = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            if (ctx.expressionSequence() != null)
                analyseDefUse(ret, ctx.expressionSequence());
            return null;
        }

        @Override
        public String visitYieldStatement(JavaScriptParser.YieldStatementContext ctx) {
            //yieldStatement
            //    : Yield ({this.notLineTerminator()}? expressionSequence)? eos
            //    ;
            PDNode ret;
            if (iteration == 1) {
                ret = new PDNode();
                ret.setLineOfCode(ctx.getStart().getLine());
                ret.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(ret);
                pdNodes.put(ctx, ret);
            } else
                ret = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            if (ctx.expressionSequence() != null)
                analyseDefUse(ret, ctx.expressionSequence());
            return null;
        }

        /**
         * 处理with语句
         * @param ctx
         * @return
         * todo 将with(object)中的object的属性加入localVars，使用完后删除
         */
        @Override
        public String visitWithStatement(JavaScriptParser.WithStatementContext ctx) {
            //withStatement
            //    : With '(' expressionSequence ')' statement
            //    ;
            PDNode ret;
            if (iteration == 1) {
                ret = new PDNode();
                ret.setLineOfCode(ctx.getStart().getLine());
                ret.setCode(getOriginalCodeText(ctx));
                ddg.addVertex(ret);
                pdNodes.put(ctx, ret);
            } else
                ret = (PDNode) pdNodes.get(ctx);

            // 将ctx.expressionSequence()中的对象的每个属性加入局部变量
//            for (JavaScriptParser.SingleExpressionContext singleExpressionContext : ctx.expressionSequence().singleExpression()) {
//                String name = singleExpressionContext.getText();
//                int type = isDefined(singleExpressionContext.getText());
//                if (type == LOCAL) {
//                    // 若为局部变量
//                    for (JavaScriptField local : localVars)
//                        if (local.NAME.equals(name)) {
//                            local.
//                        }
//                } else if (type == FIELD) {
//                    for (JavaScriptField field : activeClasses.peek().getAllFields())
//                        if (field.NAME.equals(name)) {
//
//                        }
//                } else if (type == OUTER) {
//                    for (JavaScriptClass cls : activeClasses)
//                        for (JavaScriptField field : cls.getAllFields())
//                            if (field.NAME.equals(name)) {
//
//                            }
//                } else if (name.startsWith("this.")) {
//                    if (name.startsWith("this."))
//                        name = name.substring(5);
//                } else {
//                    System.out.println("1");
//                }
//            }
//
//            // 处理完成后移除

            // Now analyse DEF-USE by visiting the expression ...
            if (ctx.expressionSequence() != null)
                analyseDefUse(ret, ctx.expressionSequence());
            return visit(ctx.statement());
        }

        @Override
        public String visitSwitchStatement(JavaScriptParser.SwitchStatementContext ctx) {
            //  switchStatement
            //    : Switch '(' expressionSequence ')' caseBlock
            //    ;
            // caseBlock
            //    : '{' caseClauses? (defaultClause caseClauses?)? '}'
            //    ;
            //
            //caseClauses
            //    : caseClause+
            //    ;
            //
            //caseClause
            //    : Case expressionSequence ':' statementList?
            //    ;
            //
            //defaultClause
            //    : Defa
            PDNode switchNode;
            if (iteration == 1) {
                switchNode = new PDNode();
                switchNode.setLineOfCode(ctx.getStart().getLine());
                switchNode.setCode("switch " + getOriginalCodeText(ctx.expressionSequence()));
                ddg.addVertex(switchNode);
                pdNodes.put(ctx, switchNode);
            } else
                switchNode = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(switchNode, ctx.expressionSequence());

            // todo 是否ok？
            for (JavaScriptParser.CaseClausesContext caseClause : ctx.caseBlock().caseClauses()) {
                for (JavaScriptParser.CaseClauseContext caseContext : caseClause.caseClause()) {
                    visit(caseContext);
                }
            }
            if (ctx.caseBlock().defaultClause() != null) {
                visit(ctx.caseBlock().defaultClause());
            }
            return null;
        }


        @Override
        public String visitThrowStatement(JavaScriptParser.ThrowStatementContext ctx) {
            //     : Throw {this.notLineTerminator()}? expressionSequence eos
            PDNode throwNode;
            if (iteration == 1) {
                throwNode = new PDNode();
                throwNode.setLineOfCode(ctx.getStart().getLine());
                // todo cfg中是这样写的，看是否妥当
                throwNode.setCode(getOriginalCodeText(ctx));
//                throwNode.setCode("throw " + getOriginalCodeText(ctx.expressionSequence()));
                ddg.addVertex(throwNode);
                pdNodes.put(ctx, throwNode);
            } else
                throwNode = (PDNode) pdNodes.get(ctx);
            //
            // Now analyse DEF-USE by visiting the expression ...
            analyseDefUse(throwNode, ctx.expressionSequence());
            return null;
        }

        @Override
        public String visitTryStatement(JavaScriptParser.TryStatementContext ctx) {
            //tryStatement
            //    : Try block (catchProduction finallyProduction? | finallyProduction)
            //    ;
            // The 'try' block has no DEF-USE effect, so no need for PDNodes;
            // just visit the 'block'
            visit(ctx.block());
            //
            // But the 'catchClause' define a local exception variable;
            // so we need to visit any available catch clauses
            if (ctx.catchProduction() != null) {
                //catchProduction
                //    : Catch ('(' assignable? ')')? block
                JavaScriptParser.CatchProductionContext cx = ctx.catchProduction();
                PDNode catchNode;
                if (iteration == 1) {
                    catchNode = new PDNode();
                    catchNode.setLineOfCode(cx.getStart().getLine());
                    // todo 看是否妥当
                    catchNode.setCode(cx.getText().substring(0, cx.getText().indexOf(')') + 1));
//                    catchNode.setCode("catch (" + getOriginalCodeText(cx.assignable()) + ")");
                    ddg.addVertex(catchNode);
                    pdNodes.put(cx, catchNode);
                } else
                    catchNode = (PDNode) pdNodes.get(cx);
                //
                // Define the exception var
                // todo js的catch应该没type
                String var = getOriginalCodeText(cx.assignable());
                JavaScriptField exceptionVar = new JavaScriptField(null, false, var);
                localVars.add(exceptionVar);
                changed |= catchNode.addDEF(var);
                //
                visit(cx.block());
                localVars.remove(exceptionVar);
            }
            if (ctx.finallyProduction() != null)
                // 'finally' block
                visit(ctx.finallyProduction().block());

            return null;
        }

        @Override
        public String visitComputedPropertyExpressionAssignment(JavaScriptParser.ComputedPropertyExpressionAssignmentContext ctx) {
            //    | '[' singleExpression ']' ':' singleExpression                                 # ComputedPropertyExpressionAssignment
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitFunctionProperty(JavaScriptParser.FunctionPropertyContext ctx) {
            //    | Async? '*'? propertyName '(' formalParameterList?  ')'  functionBody  # FunctionProperty
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitPropertyGetter(JavaScriptParser.PropertyGetterContext ctx) {
            //    | getter '(' ')' functionBody                                           # PropertyGetter
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitPropertySetter(JavaScriptParser.PropertySetterContext ctx) {
            //    | setter '(' formalParameterArg ')' functionBody                        # PropertySetter
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitPropertyShorthand(JavaScriptParser.PropertyShorthandContext ctx) {
            //    | Ellipsis? singleExpression                                                    # PropertyShorthand
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitPropertyName(JavaScriptParser.PropertyNameContext ctx) {
            return getOriginalCodeText(ctx);

        }

        /**
         * 访问函数/方法调用的参数列表
         * @param ctx
         * @return
         */
        @Override
        public String visitArguments(JavaScriptParser.ArgumentsContext ctx) {
            return getOriginalCodeText(ctx);

        }

        @Override
        public String visitArgument(JavaScriptParser.ArgumentContext ctx) {
            return ctx.singleExpression() == null ? getOriginalCodeText(ctx) : visit(ctx.singleExpression());
        }

        @Override
        public String visitTemplateStringExpression(JavaScriptParser.TemplateStringExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitTernaryExpression(JavaScriptParser.TernaryExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitLogicalAndExpression(JavaScriptParser.LogicalAndExpressionContext ctx) {
            //
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " && " + expr2 + ')';

        }

        @Override
        public String visitPowerExpression(JavaScriptParser.PowerExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitPreIncrementExpression(JavaScriptParser.PreIncrementExpressionContext ctx) {
            // '++' expression
            String expr = visit(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
                defList.add(expr);
                selfFlowList.add(expr);
            }
            return "++" + expr;
        }

        @Override
        public String visitObjectLiteralExpression(JavaScriptParser.ObjectLiteralExpressionContext ctx) {
            // objectLiteral
            //    : '{' (propertyAssignment (',' propertyAssignment)* ','?)? '}'
            //    ;
            //propertyAssignment
            //    : propertyName ':' singleExpression                                             # PropertyExpressionAssignment
            //    ;
            StringBuilder memberList = new StringBuilder(visit(ctx.objectLiteral().propertyAssignment(0)));
            for (int i = 1; i < ctx.objectLiteral().propertyAssignment().size(); ++i) {
                String member = visit(ctx.objectLiteral().propertyAssignment(i));
                memberList.append(", ").append(member);

                // 将propertyName连同前面的类名一起加入局部变量
                String memberName = member.substring(0, member.indexOf(":"));
                localVars.add(new JavaScriptField(null, false, memberName));
            }
            return memberList.toString();
        }

        @Override
        public String visitPropertyExpressionAssignment(JavaScriptParser.PropertyExpressionAssignmentContext ctx) {
            // propertyAssignment
            //    : propertyName ':' singleExpression                                             # PropertyExpressionAssignment
            //    ;
            return visit(ctx.propertyName()) + ": " + visit(ctx.singleExpression());
        }

        @Override
        public String visitMetaExpression(JavaScriptParser.MetaExpressionContext ctx) {
            return getOriginalCodeText(ctx);

        }

        @Override
        public String visitInExpression(JavaScriptParser.InExpressionContext ctx) {
            return super.visitInExpression(ctx);
        }

        @Override
        public String visitLogicalOrExpression(JavaScriptParser.LogicalOrExpressionContext ctx) {
            // expression '||' expression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " || " + expr2 + ')';
        }

        @Override
        public String visitOptionalChainExpression(JavaScriptParser.OptionalChainExpressionContext ctx) {
            return getOriginalCodeText(ctx);

        }

        @Override
        public String visitNotExpression(JavaScriptParser.NotExpressionContext ctx) {
            String expr = visit(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
            }
            return "!" + expr;
        }

        @Override
        public String visitPreDecreaseExpression(JavaScriptParser.PreDecreaseExpressionContext ctx) {
            // '--' expression
            String expr = visit(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
                defList.add(expr);
                selfFlowList.add(expr);
            }
            return "--" + expr;
        }

        @Override
        public String visitArgumentsExpression(JavaScriptParser.ArgumentsExpressionContext ctx) {
            //     | singleExpression arguments                                            # ArgumentsExpression
            String callee = null;
            String callExpression = visit(ctx.singleExpression());
            String functionName = callExpression;
            Logger.debug("---");
            Logger.debug("CALL EXPR : " + functionName);
            int start = 0, lastDot = callExpression.lastIndexOf('.');
            if (lastDot > 0) {
                //start = callExpression.substring(0, lastDot).lastIndexOf('.');
                //if (start < 0)
                //	start = 0;
                //else
                //	++start;

                // 若有.运算符
                // 获取调用该函数/方法的对象
                callee = callExpression.substring(start, lastDot);
                Logger.debug("HAS CALLEE : " + callee);
                if (isUsableExpression(callee)) {
                    // 对该对象的使用看作use
                    useList.add(callee);
                    Logger.debug("CALLEE IS USABLE");
                }
                // 记录函数名
                functionName = callExpression.substring(lastDot + 1);
            } else {
                Logger.debug("NO CALLEE");
                // 若无.运算符，函数名就是整个调用表达式
                functionName = callExpression;
            }
            // 查找该函数/方法的定义信息
            MethodDefInfo defInfo = findDefInfo(callee, functionName, ctx.arguments());
            Logger.debug("FIND DEF RESULT: " + defInfo);
            Logger.debug("---");
            if (callee != null && defInfo != null && defInfo.doesStateDEF())
                defList.add(callee);
            return callExpression + '(' + visitFunctionArgs(ctx.arguments(), defInfo) + ')';
        }

        private String visitFunctionArgs(JavaScriptParser.ArgumentsContext ctx, MethodDefInfo defInfo) {
            // arguments
            //    : '('(argument (',' argument)* ','?)?')'
            // 若参数列表不为空
            if (ctx != null && ctx.argument() != null && !ctx.argument().isEmpty()) {
                StringBuilder args = new StringBuilder();
                List<JavaScriptParser.ArgumentContext> argsList = ctx.argument();
                String arg = visit(argsList.get(0));
                args.append(arg);
                if (isUsableExpression(arg)) {
                    useList.add(arg);
                    if (defInfo != null && defInfo.argDEFs()[0])
                        defList.add(arg);
                }
                for (int i = 1; i < argsList.size(); ++i) {
                    arg = visit(argsList.get(i));
                    args.append(", ").append(arg);
                    if (isUsableExpression(arg)) {
                        useList.add(arg);
                        if (defInfo != null && defInfo.argDEFs()[i])
                            defList.add(arg);
                    }
                }
                return args.toString();
            } else
                return "";
        }

        @Override
        public String visitAwaitExpression(JavaScriptParser.AwaitExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitThisExpression(JavaScriptParser.ThisExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitFunctionExpression(JavaScriptParser.FunctionExpressionContext ctx) {
            return getOriginalCodeText(ctx);

        }

        @Override
        public String visitUnaryMinusExpression(JavaScriptParser.UnaryMinusExpressionContext ctx) {
            String expr = visit(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
                defList.add(expr);
                selfFlowList.add(expr);
            }
            return "-" + expr;
        }

        @Override
        public String visitAssignmentExpression(JavaScriptParser.AssignmentExpressionContext ctx) {
            // <assoc=right> singleExpression '=' singleExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1)) {
                if (!ctx.getChild(1).getText().equals("="))
                    useList.add(expr1);
                defList.add(expr1);
            }
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " $ASSIGN " + expr2 + ')';
        }

        @Override
        public String visitPostDecreaseExpression(JavaScriptParser.PostDecreaseExpressionContext ctx) {
            //singleExpression {this.notLineTerminator()}? '--'
            String expr = visit(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
                defList.add(expr);
            }
            return expr + "--";
        }

        @Override
        public String visitTypeofExpression(JavaScriptParser.TypeofExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitInstanceofExpression(JavaScriptParser.InstanceofExpressionContext ctx) {
            // singleExpression Instanceof singleExpression
            String expr = visit(ctx.singleExpression(0));
            if (isUsableExpression(expr)) {
                useList.add(expr);
            }
            // the parethesis are added to mark this expression as used
            return '(' + expr + " $INSTANCE " + ctx.singleExpression(1).getText() + ')';
        }

        @Override
        public String visitUnaryPlusExpression(JavaScriptParser.UnaryPlusExpressionContext ctx) {
            String expr = visit(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
                defList.add(expr);
                selfFlowList.add(expr);
            }
            return "+" + expr;
        }

        @Override
        public String visitDeleteExpression(JavaScriptParser.DeleteExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitImportExpression(JavaScriptParser.ImportExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitEqualityExpression(JavaScriptParser.EqualityExpressionContext ctx) {
            // singleExpression ('==' | '!=' | '===' | '!==') singleExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " $EQL " + expr2 + ')';
        }

        @Override
        public String visitBitXOrExpression(JavaScriptParser.BitXOrExpressionContext ctx) {
            // expression '^' expression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " ^ " + expr2 + ')';
        }

        @Override
        public String visitSuperExpression(JavaScriptParser.SuperExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitMultiplicativeExpression(JavaScriptParser.MultiplicativeExpressionContext ctx) {
            // | singleExpression ('*' | '/' | '%') singleExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            String op;
            switch(ctx.getChild(1).getText().charAt(0)) {
                case '*':
                    op = " $MUL ";
                    break;
                case '/':
                    op = " $DIV ";
                    break;
                default:
                    op = " $MOD ";
                    break;
            }
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + op + expr2 + ')';
        }

        @Override
        public String visitBitShiftExpression(JavaScriptParser.BitShiftExpressionContext ctx) {
            // singleExpression ('<<' | '>>' | '>>>') singleExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " $SHIFT " + expr2 + ')';
        }

        @Override
        public String visitParenthesizedExpression(JavaScriptParser.ParenthesizedExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitAdditiveExpression(JavaScriptParser.AdditiveExpressionContext ctx) {
            // singleExpression ('+' | '-') singleExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            String op;
            switch(ctx.getChild(1).getText().charAt(0)) {
                case '+':
                    op = " $ADD ";
                    break;
                default:
                    op = " $SUB ";
                    break;
            }
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + op + expr2 + ')';
        }

        @Override
        public String visitRelationalExpression(JavaScriptParser.RelationalExpressionContext ctx) {
            // singleExpression ('<' | '>' | '<=' | '>=') singleExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " $COMP " + expr2 + ')';
        }

        @Override
        public String visitPostIncrementExpression(JavaScriptParser.PostIncrementExpressionContext ctx) {
            //singleExpression {this.notLineTerminator()}? '++'
            String expr = visitSingleExpression(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
                defList.add(expr);
            }
            return expr + "++";
        }

        @Override
        public String visitYieldExpression(JavaScriptParser.YieldExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitBitNotExpression(JavaScriptParser.BitNotExpressionContext ctx) {
            String expr = visitSingleExpression(ctx.singleExpression());
            if (isUsableExpression(expr)) {
                useList.add(expr);
            }
            return "~" + expr;
        }

        @Override
        public String visitNewExpression(JavaScriptParser.NewExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitLiteralExpression(JavaScriptParser.LiteralExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }


        @Override
        public String visitArrayLiteralExpression(JavaScriptParser.ArrayLiteralExpressionContext ctx) {
            // arrayLiteral
            //    : ('[' elementList ']')
            //    ;
            return '[' + visit(ctx.arrayLiteral().elementList()) + ']';
        }
        @Override
        public String visitElementList(JavaScriptParser.ElementListContext ctx) {
            // elementList
            //    : ','* arrayElement? (','+ arrayElement)* ','* // Yes, everything is optional
            //    ;
            StringBuilder eleList = new StringBuilder(visit(ctx.arrayElement(0)));
            for (int i = 1; i < ctx.arrayElement().size(); ++i)
                eleList.append(", ").append(visit(ctx.arrayElement(i)));
            return eleList.toString();
        }

        @Override
        public String visitArrayElement(JavaScriptParser.ArrayElementContext ctx) {
            // arrayElement
            //    : Ellipsis? singleExpression
            //    ;
            String ele = visit(ctx.singleExpression());
            if (isUsableExpression(ele))
                useList.add(ele);
            return ele;
        }

        @Override
        public String visitIdentifierName(JavaScriptParser.IdentifierNameContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitMemberDotExpression(JavaScriptParser.MemberDotExpressionContext ctx) {
            //     | singleExpression '?'? '.' '#'? identifierName                         # MemberDotExpression
            String object = visit(ctx.singleExpression());
            String member = visit(ctx.identifierName());
            if (isUsableExpression(object)) {
                useList.add(object);
            }
            StringBuilder chs = new StringBuilder();
            for (int i = 1; i < ctx.getChildCount() - 1; i++) {
                if (ctx.getChild(i) != null)
                    chs.append(ctx.getChild(1).getText());
            }
            localVars.add(new JavaScriptField(null, false, object + '.' + member));
            return object + chs + member;
        }

        @Override
        public String visitClassExpression(JavaScriptParser.ClassExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitMemberIndexExpression(JavaScriptParser.MemberIndexExpressionContext ctx) {
            //     | singleExpression '?.'? '[' expressionSequence ']'                     # MemberIndexExpression
            String array = visit(ctx.singleExpression());
            if (isUsableExpression(array))
                useList.add(array);
            String index = visit(ctx.expressionSequence());
            if (isUsableExpression(index))
                useList.add(index);
            StringBuilder ch = new StringBuilder();
            for (int i = 1; i < ctx.getChildCount() - 1; i++) {
                if (ctx.getChild(i) != null && "?.".equals(ctx.getChild(i).getText()))
                    ch.append(ctx.getChild(1).getText());
            }
            return array + ch + '[' + index + ']';
        }

        @Override
        public String visitIdentifierExpression(JavaScriptParser.IdentifierExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitBitAndExpression(JavaScriptParser.BitAndExpressionContext ctx) {
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " & " + expr2 + ')';
        }

        @Override
        public String visitBitOrExpression(JavaScriptParser.BitOrExpressionContext ctx) {
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " | " + expr2 + ')';
        }

        @Override
        public String visitAssignmentOperatorExpression(JavaScriptParser.AssignmentOperatorExpressionContext ctx) {
            // <assoc=right> singleExpression '=' singleExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1)) {
                useList.add(expr1);
                defList.add(expr1);
            }
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return '(' + expr1 + " $ASSIGN " + expr2 + ')';

        }

        @Override
        public String visitVoidExpression(JavaScriptParser.VoidExpressionContext ctx) {
            return getOriginalCodeText(ctx);
        }

        @Override
        public String visitCoalesceExpression(JavaScriptParser.CoalesceExpressionContext ctx) {
            //     | singleExpression '??' singleExpression                                # CoalesceExpression
            String expr1 = visit(ctx.singleExpression(0));
            String expr2 = visit(ctx.singleExpression(1));
            if (isUsableExpression(expr1))
                useList.add(expr1);
            if (isUsableExpression(expr2))
                useList.add(expr2);
            // the parethesis are added to mark this expression as used
            return expr1 + " ?? " + expr2;
        }

        /***********************************************
         ***********************************************
         ***       NON-DETERMINANT EXPRESSIONS       ***
         ***********************************************
         ***********************************************/


        /**
         * Check to see if the given expression is USABLE.
         * An expression is usable if we are required to add it to the USE-list.
         * Any expression who is DEFINABLE should be added to the USE-list.
         * An expression is definable, if it holds a value which can be modified in the program.
         * For example, Class names and Class types are not definable.
         * Method invocations are not definable.
         * Literals are also not definable.
         */
        private boolean isUsableExpression(String expr) {
            // must not be a literal or of type 'class'.
            if (expr == null || expr == "") {
                return false;
            }
            if (expr.startsWith("$"))
                return false;
            // must not be a method-call or parenthesized expression
            if (expr.endsWith(")"))
                return false;
            // must not be an array-indexing expression
            if (expr.endsWith("]"))
                return false;
            // must not be post unary operation expression
            if (expr.endsWith("++") || expr.endsWith("--"))
                return false;
            // must not be a pre unary operation expression
            if (expr.startsWith("+") || expr.startsWith("-") || expr.startsWith("!") || expr.startsWith("~"))
                return false;
            // must not be an array initialization expression
            if (expr.endsWith("}"))
                return false;
            // must not be an explicit generic invocation expression
            if (expr.startsWith("<"))
                return false;
            //
            return true;
        }



//        @Override
//        public String visitNewExpression(JavaScriptParser.NewExpressionContext ctx) {
//            return super.visitNewExpression(ctx);
//        }

        /**
         * Visit the list of arguments of a method call, and return a proper string.
         * This method will also add usable expressions to the USE-list.
         */
        private String visitMethodArgs(JavaParser.ExpressionListContext ctx, MethodDefInfo defInfo) {
            // expressionList :  expression (',' expression)*
//            if (ctx != null) {
//                StringBuilder args = new StringBuilder();
//                List<JavaScriptParser.ExpressionContext> argsList = ctx.expression();
//                String arg = visit(argsList.get(0));
//                args.append(arg);
//                if (isUsableExpression(arg)) {
//                    useList.add(arg);
//                    if (defInfo != null && defInfo.argDEFs()[0])
//                        defList.add(arg);
//                }
//                for (int i = 1; i < argsList.size(); ++i) {
//                    arg = visit(argsList.get(i));
//                    args.append(", ").append(arg);
//                    if (isUsableExpression(arg)) {
//                        useList.add(arg);
//                        if (defInfo != null && defInfo.argDEFs()[i])
//                            defList.add(arg);
//                    }
//                }
//                return args.toString();
//            } else
//                return "";
            return "";
        }


        /*****************************************************
         *****************************************************
         *****************************************************/

        /**
         * Get the original program text for the given parser-rule context.
         * This is required for preserving whitespaces.
         */
        private String getOriginalCodeText(ParserRuleContext ctx) {
            int start = ctx.start.getStartIndex();
            int stop = ctx.stop.getStopIndex();
            Interval interval = new Interval(start, stop);
            return ctx.start.getInputStream().getText(interval);
        }
    }
}

/**
 * A simple structure to store DEF information about a Java method.
 *
 * @author Seyed Mohammad Ghaffarian
 */
class MethodDefInfo {

    // Method ID
    public final String NAME;
//    public final String PACKAGE;
    public final String RET_TYPE;
    public final String CLASS_NAME;
//    public final String[] PARAM_TYPES;

    // DEF Info
    private boolean stateDEF;
    private boolean[] argDEFs;
    private List<String> fieldDEFs;

    public MethodDefInfo(String ret, String name, String cls) {
        NAME = name;
        RET_TYPE = ret;
        CLASS_NAME = cls;
        //
        fieldDEFs = new ArrayList<>();
        stateDEF = guessByTypeOrName();
    }

//    public MethodDefInfo(String ret, String name, String pkg, String cls, String[] args) {
//        NAME = name;
//        RET_TYPE = ret;
//        CLASS_NAME = cls;
//        PACKAGE = pkg == null ? "" : pkg;
//        PARAM_TYPES = args == null ? new String[0] : args;
//        //
//        fieldDEFs = new ArrayList<>();
//        stateDEF = guessByTypeOrName();
//        argDEFs = new boolean[PARAM_TYPES.length];  // all initialized to 'false'
//    }

    private boolean guessByTypeOrName() {
        // First check if this method is a constructor ...
        if (RET_TYPE == null)
            return true;
        // If not, then try to guess by method-name ...
        String[] prefixes = {"set", "put", "add", "insert", "push", "append"};
        for (String pre : prefixes)
            if (NAME.toLowerCase().startsWith(pre))
                return true;
        return false;
    }

    public boolean doesStateDEF() {
        return stateDEF;
    }

    public void setStateDEF(boolean stateDef) {
        stateDEF = stateDef;
    }

    public boolean[] argDEFs() {
        return argDEFs;
    }

    public void setArgDEF(int argIndex, boolean def) {
        argDEFs[argIndex] = def;
    }

    public void setAllArgDEFs(boolean[] argDefs) {
        argDEFs = argDefs;
    }

    public String[] fieldDEFs() {
        return fieldDEFs.toArray(new String[fieldDEFs.size()]);
    }

    public void addFieldDEF(String fieldName) {
        if (!fieldDEFs.contains(fieldName)) {
            fieldDEFs.add(fieldName);
            stateDEF = true;
        }
    }

        @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof MethodDefInfo))
            return false;
        MethodDefInfo info = (MethodDefInfo) obj;
        return this.NAME.equals(info.NAME) && this.CLASS_NAME.equals(info.CLASS_NAME)
                && this.RET_TYPE.equals(info.RET_TYPE);
    }

    @Override
    public String toString() {
        String retType = RET_TYPE == null ? "null" : RET_TYPE;
//        String args = PARAM_TYPES == null ? "null" : Arrays.toString(PARAM_TYPES);
        StringBuilder str = new StringBuilder();
        str.append("{ TYPE : \"").append(retType).append("\", ");
        str.append("NAME : \"").append(NAME).append("\", ");
//        str.append("ARGS : ").append(args).append(", ");
        str.append("CLASS : \"").append(CLASS_NAME).append("\", ");
//        str.append("PACKAGE : \"").append(PACKAGE).append("\" }");
        return str.toString();
    }
}
